//TODO:Real OnSize should be generated only when client rect changes
//TODO:More uses for FLAG_SIZECHANGEREQUESTED
//TODO:Also enqueue scrolls
//and use it in CreateForeign
//XXX:Under some circumstances... unknown... probably after the first time
//a window is mapped, XTranslateCoordinates takes a looooong time
//it would be nice to have a version which gives cached results according to
//our own tree, especially for immediate parents of windows
#include <buola/gui/cwindow.h>

#include <buola/gui/cattachmanager.h>
#include <buola/gui/ctoolkit.h>
#include <buola/mat/transformations.h>
#include <buola/gui/cwindowmanager.h>
#include <buola/gui/cexposehelper.h>
#include <buola/gui/cphysicalwindow.h>
#include <buola/gui/cicon.h>
#include <buola/image/cgc.h>

namespace buola { namespace gui {

///////////////////////////////////
// constructor and destructor
//

CWindow::CWindow()
    :   mPhysical(nullptr)
    ,   mParent(nullptr)
    ,   mFocusTimestamp(CCalendarTime::min())
    ,   mFlags(EFlags::NONE)
    ,   mPaintMode(EPaintMode::NORMAL)
    ,   mFocusMode(EFocusMode::NONE)
    ,   mInputMode(EInputMode::NORMAL)
    ,   mAttribs(EAttribs::MAPPABLE)
    ,   mWindowType(EWindowType::NORMAL)
    ,   mWindowActions(EWindowActions::ALL)
    ,   mEvents(EVENT_DEFAULT)
    ,   mBackColor(1.0,1.0,1.0)
    ,   mPointer(CPointer::GetStock(CPointer::STOCK_DEFAULT))
    ,   mWholeRect(nUndefined)
    ,   mClientRect(nUndefined)
    ,   mPosPreferred(nUndefined)
    ,   mSizeMin(nUndefined)
    ,   mSizeMax(nUndefined)
    ,   mSizePreferred(nUndefined)
    ,   mSizeStep(nUndefined)
    ,   mValid(EValid::NONE)
{
    window_manager().Add(this);
}

CWindow::~CWindow()
{
    window_manager().Remove(this);
}

bool CWindow::Close(bool pForce)
{
    msg_info() << "close received... should implement\n";
//     if(WindowActions()&EWindowActions::HIDE)
//     {
//         Hide();
//         return true;
//     }
// 
//     if(!pForce)
//     {
//         if(!CanClose(true,false)) return false;
//     }
// 
//     ///\todo now we should probably unmap it and notify the derived class
    return true;
}

void CWindow::SetWindowType(EWindowType pType)
{
    if(pType==mWindowType) return;

    mWindowType=pType;

    ///\todo this
//    if(mPhysical&&mPhysical->IsWindowManaged())
//        mPhysical->UpdateState();
}

void CWindow::SetWindowActions(EWindowActions pActions)
{
    mWindowActions=pActions;
}

////////////////////////////////////////////////////////////////
//                                                            //
//                    public interface                        //
//                                                            //
////////////////////////////////////////////////////////////////
/*
void CWindow::WhenUnmapped()
{
    UnsetFlag(FLAG_MAPPED)
    
    if(mPointerWindow==this)
        mPointerWindow=nullptr;

    //maybe something like this
    OnPreDestroy();
    OnDestroy();
    
    bool lFinished=false;

    //children are not necessarily unmapped yet, and they should certainly keep their structure
    //but they should also release grabs and so on
    while(GetFlag(FLAG_HASAGRAB))
        ReleasePointer();

    DropFocus();

    if(mParent)
        mParent->mChildren.erase(std::find(mParent->mChildren.begin(),
                            mParent->mChildren.end(),this));

    if(GetFlag(FLAG_MODAL))
    {
        get_toolkit()->ExitModal(this);
    }

    if(GetFlag(FLAG_VISIBLE)&&mParent&&!mParent->GetFlag(FLAG_DESTROYED))
    {
        mParent->InvalidateRegions(REGION_CLIENTVISIBLE);

        if(!GetFlag(FLAG_PHYSICAL))
            mParent->Refresh(REFRESH_DONTEXPOSE);
    }

    if(mParent)
    {
        mParent->SendChildEvent(this,CHILD_DESTROY);
    }

    mAttachManager=nullptr;

    if(mLayoutManagers.size())
    {
        std::vector<CLayoutManager*> lManagers=mLayoutManagers;

        for(int i=0;i<lManagers.size();i++)
            lManagers[i]->OnDestroy();
    }

    DestroyPhysical();

    mParent=nullptr;
    mFocusedChild=nullptr;

    mAllWindows.erase(std::find(mAllWindows.begin(),mAllWindows.end(),this));
    mLoopFunction->mRefresh.erase(this);
    mLoopFunction->mOnSize.erase(this);
    mLoopFunction->mOnMove.erase(this);

    if(GetFlag(FLAG_MAINWINDOW))
    {
        if(!get_main_window())
            buola_exit();
    }

    mFlags=FLAG_DESTROYED;  //it's the only flag a destroyed window can have

    if(GetAutoDelete())
        delete this;
}
*/
///////////////
// redrawing
//

void CWindow::SetPaintMode(EPaintMode pMode)
{
    mPaintMode=pMode;
    if(Mapped())
    {
        ///\todo this
        //GetPhysical()->OnPaintModeChanged(this);
    }
}

void CWindow::SetOpacity(double pOpacity)
{
    ///\todo get this working also for non-toplevel windows
    
    if(mPhysical)
        mPhysical->SetOpacity(pOpacity);
}

void CWindow::Refresh(const CRegion &pRegion,bool pImmediate)
{
    if(!Visible()) return;
    
    ///\todo some windows might want to have a triple buffer. in this case, they will draw to their own buffer when rendering,
    ///keep track of the dirtyness of that buffering, and then draw to the framebuffer when asked to.
    ///\todo offset and transform region!!!
    mRegionDirty|=pRegion;

    if(pImmediate)
        GetTopLevel()->DoRefresh(CRegion());
    else
        window_manager().mRefresh.insert(GetTopLevel());
}

void CWindow::Relayout(bool pExternal)
{
    if(Mapped())
    {
        ///\todo something more
        //When there is a change of parent, we must do some things!!
//        FreezeModal(nullptr,true);

        if(pExternal)
            Invalidate(EValid::EXTERNAL_LAYOUT);
        else
            Invalidate(EValid::INTERNAL_LAYOUT);
        window_manager().mLayout.insert(GetTopLevel());
    }
    
}

////////////////
// visibility
//

void CWindow::Show(bool pShow)
{
    if(pShow==GetAttrib(EAttribs::MAPPABLE)) return;
    
    if(pShow)
        SetAttrib(EAttribs::MAPPABLE);
    else
        UnsetAttrib(EAttribs::MAPPABLE);
    
    if(mParent)
    {
        if(GetFlag(EFlags::ATTACHED))
            mParent->Invalidate(EValid::EXTERNAL_LAYOUT);
        else
            mParent->Invalidate(EValid::INTERNAL_LAYOUT);
        window_manager().mLayout.insert(GetTopLevel());
    }
}

void CWindow::Freeze()
{
    if(GetFlag(EFlags::FROZEN)) return;

    SetFlag(EFlags::FROZEN);

    if(mPhysical)
        mPhysical->Freeze(true);
}

void CWindow::Defreeze()
{
    if(!GetFlag(EFlags::FROZEN)) return;
    UnsetFlag(EFlags::FROZEN);

    if(mPhysical)
        mPhysical->Freeze(true);
}

bool CWindow::Clickable()
{
    return (GetFlag(EFlags::MAPPED)&&!GetFlag(EFlags::FROZEN)&&(!mParent||mParent->Clickable()));
}

//////////////////////////
// window relationships
//

void CWindow::Place(const PWindow &pChild)
{
    if(pChild->Parent())
    {
        if(pChild->Parent()==this) return;
        pChild->Parent()->RemoveChild(pChild);
    }

    //acquiring new parent
    pChild->mParent=this;
    
    mChildren.push_back(pChild);

    Relayout();
}

void CWindow::Attach(const PWindow &pChild,EAttachPlace pPlace,EAttachFlags pFlags)
{
    Place(pChild);
    pChild->SetFlag(EFlags::ATTACHED);
    
    AttachManager()->Attach(pChild,pPlace,pFlags);
}

void CWindow::Attach(const PWindow &pH,const PWindow &pV,EAttachPlace pPlace)
{
    Place(pH);
    pH->SetFlag(EFlags::ATTACHED);
    Place(pV);
    pV->SetFlag(EFlags::ATTACHED);
    
    AttachManager()->AttachScroll(pH,pV,pPlace);
}

void CWindow::Attach(const PWindow &pChild,EAttachRelative pPlace,const PWindow &pOther,EAttachFlags pFlags)
{
    Place(pChild);
    pChild->SetFlag(EFlags::ATTACHED);

    if(pOther->Parent()==this)
        AttachManager()->AttachRelative(pChild,pPlace,pOther,pFlags);
    else
        AttachManager()->Attach(pChild,ATTACH_EAST,pFlags);
}

void CWindow::Manage(const PWindow &pChild)
{
    Place(pChild);
    pChild->SetFlag(EFlags::MANAGED);
}

void CWindow::RemoveChild(const PWindow &pChild)
{
    if(this!=pChild->Parent()) return;
    
    if(pChild->Mapped())
        pChild->Unmap();

    mChildren.erase(std::find(mParent->mChildren.begin(),mParent->mChildren.end(),this));

    pChild->mParent=nullptr;
    ///\todo many more things
}

CWindow *CWindow::GetTopLevel()
{
    if(!mParent) return this;
    return mParent->GetTopLevel();
}

CWindow *CWindow::GetPhysicalAncestor()
{
    if(mPhysical) return this;
    return mParent?mParent->GetPhysicalAncestor():nullptr;
}

bool CWindow::IsDescendantOf(CWindow &pW)
{
    return pW.IsAncestorOf(*this);
}

bool CWindow::IsParentOf(CWindow &pW)
{
    return (this==pW.mParent);
}

bool CWindow::IsAncestorOf(CWindow &pW)
{
    for(CWindow *lW=&pW;lW;lW=lW->mParent)
    {
        if(lW==this) return true;
    }

    return false;
}

bool CWindow::IsSiblingOf(CWindow &pW)
{
    return (mParent==pW.mParent);
}

CWindow *CWindow::FindMouseTargetAt(const CPoint_d &pPoint)
{
    for(const PWindow &c : mChildren)
    {
        if(!c->Clickable()) continue;

        if(c->WholeRect().Contains(pPoint))
            return c->FindMouseTargetAt(pPoint-c->mWholeRect.TopLeft());
    }

    return this;
}

/////////////////////
// size & position
//

void CWindow::SetMinSize(const CSize_d &pMinSize)
{
    mSizeMin=pMinSize;
}

void CWindow::SetMaxSize(const CSize_d &pMaxSize)
{
    mSizeMax=pMaxSize;
}

void CWindow::SetPreferredSize(const CSize_d &pPreferredSize)
{
    mSizePreferred=pPreferredSize;
}

void CWindow::SetFixedSize(const CSize_d &pSize)
{
    mSizeMin=pSize;
    mSizeMax=pSize;
}

void CWindow::SetSizeStep(const CSize_d &pSizeStep)
{
    mSizeStep=pSizeStep;
}

void CWindow::Map(const CPoint_d &pPos,const CSize_d &pSize,bool pClientCoordinates)
{
    //calculate the initial geometry (try to set sane values if not specified)
    CPoint_d lPos=pPos;
    CSize_d lSize=pSize;

    if(lPos.Undefined())
    {
        lPos=mPosPreferred;
        if(lPos.Undefined())
            lPos={0,0};
    }
    if(lSize.Undefined())
    {
        lSize=mSizePreferred;
        if(lSize.Undefined())
            lSize={400,300};
    }

    if(!mParent)
    {
        if(!mPhysical)
        {
            mPhysical=get_toolkit()->GetNewPhysicalWindow(this);
            mPhysical->Create(nullptr,lPos,lSize);
            ///\todo the frame should do this
            //mPhysical->SetTitle(mTitle);
        }
        else
        {
            mPhysical->MoveResize(lPos,lSize);
        }

        mPhysical->Map(true);
    }
    else
    {
        if(pClientCoordinates) ///\todo apply also clientmatrix
            lPos+=mParent->mClientRect.TopLeft();

        ProcessMap(CRect_d(lPos,lSize));
    }
}

void CWindow::Unmap()
{
    if(!mParent)
    {
        if(GetFlag(EFlags::MODAL))
        {
            window_manager().ExitModal(this);
        }
    }

    if(GetFlag(EFlags::MAPONLAYOUT))
        ProcessUnmap();

    if(mPhysical)
    {
        ///\todo maybe we shouldn't do this every time the window gets unmapped. Or maybe unmapped should only mean destroyed
        mPhysical->Destroy();
        delete mPhysical;
        mPhysical=nullptr;

        ///\todo this should go in other windows
        /*
        if(GetFlag(FLAG_VISIBLE)&&mParent&&!mParent->GetFlag(FLAG_DESTROYED))
        {
            mParent->InvalidateRegions(REGION_CLIENTVISIBLE);

            if(!GetFlag(FLAG_PHYSICAL))
                mParent->Refresh(REFRESH_DONTEXPOSE);
        }

        if(mParent)
        {
            mParent->SendChildEvent(this,CHILD_DESTROY);
        }
        */


        ///\todo remove from window manager in some way
        //mLoopFunction->mRefresh.erase(this);
        //mLoopFunction->mOnSize.erase(this);
        //mLoopFunction->mOnMove.erase(this);

        ///\todo maybe we should just tell the window manager that we don't exist anymore. Then it will check if we were the last
        ///main window and exit automatically, as well as remove from lists
        /*
        if(GetFlag(FLAG_MAINWINDOW))
        {
            if(!get_main_window())
                buola_exit();
        }
        */
    }
}

void CWindow::Move(const CPoint_d &pPos)
{
    ///\todo something different if it is a frame
    if(pPos==mPosPreferred) return;
    mPosPreferred=pPos;

    if(mParent)
    {
        if(mParent->Mapped())
        {
            if(GetFlag(EFlags::ATTACHED))
                mParent->Invalidate(EValid::EXTERNAL_LAYOUT);
            else
                mParent->Invalidate(EValid::INTERNAL_LAYOUT);
            window_manager().mLayout.insert(GetTopLevel());
        }
    }
    else if(mPhysical)
    {
        mPhysical->Move(pPos);
    }
}

void CWindow::Resize(const CSize_d &pSize)
{
    ///\todo something different if it is a frame
    if(pSize==mSizePreferred) return;
    mSizePreferred=pSize;

    if(mParent)
    {
        if(mParent->Mapped())
        {
            if(GetFlag(EFlags::ATTACHED))
                mParent->Invalidate(EValid::EXTERNAL_LAYOUT);
            else
                mParent->Invalidate(EValid::INTERNAL_LAYOUT);
            window_manager().mLayout.insert(GetTopLevel());
        }
    }
    else if(mPhysical)
    {
        mPhysical->Resize(pSize);
    }
}

void CWindow::MoveResize(const CPoint_d &pPos,const CSize_d &pSize)
{
    ///\todo something different if it is a frame
    if(pPos==mPosPreferred&&pSize==mSizePreferred) return;
    mPosPreferred=pPos;
    mSizePreferred=pSize;

    if(mParent)
    {
        if(mParent->Mapped())
        {
            if(GetFlag(EFlags::ATTACHED))
                mParent->Invalidate(EValid::EXTERNAL_LAYOUT);
            else
                mParent->Invalidate(EValid::INTERNAL_LAYOUT);
            window_manager().mLayout.insert(GetTopLevel());
        }
    }
    else if(mPhysical)
    {
        mPhysical->MoveResize(pPos,pSize);
    }
}

/////////////////////////// Z-order ///////////////////////////

void CWindow::BringToTop()
{
    if(mPhysical)
    {
        mPhysical->Raise({});
        return;
    }

    auto i=std::find(mParent->mChildren.begin(),mParent->mChildren.end(),this);
    assert(i!=mParent->mChildren.end());
    std::rotate(mParent->mChildren.begin(),i,i+1);

    if(Mapped())
    {
        Invalidate(EValid::REGIONS);
        Refresh();
    }
}

void CWindow::SendToBottom()
{
    if(mPhysical)
    {
        mPhysical->Lower();
        return;
    }

    auto i=std::find(mParent->mChildren.begin(),mParent->mChildren.end(),this);
    assert(i!=mParent->mChildren.end());
    std::rotate(i,i+1,mParent->mChildren.end());

    if(Mapped())
    {
        Invalidate(EValid::REGIONS);
        Refresh();
    }
}

//////////////////////// layout managers ////////////////////////

CAttachManager *CWindow::AttachManager(bool pCreate)
{
    if(!mAttachManager&&pCreate)
        mAttachManager.reset(new CAttachManager(this));

    return mAttachManager.get();
}

CPoint_d CWindow::GetMousePos()
{
    return get_toolkit()->GetMousePos(this)-mClientRect.TopLeft();
}

CPoint_d CWindow::Position()
{
    if(mParent)
        return mWholeRect.TopLeft()-mParent->mClientRect.TopLeft();
    else
        return mWholeRect.TopLeft();
}

CPoint_d CWindow::GetPosInTopLevel()
{
    if(mParent)
        return mParent->GetPosInTopLevel()+mWholeRect.TopLeft();
    return CPoint_d(0,0);
}

CPoint_d CWindow::GetRootPos()
{
    if(mParent)
        return mParent->GetRootPos()+mWholeRect.TopLeft();
    return mWholeRect.TopLeft();
}

const CRegion &CWindow::GetWholeRegion()
{
    if(!Valid(EValid::REGION_WHOLE))
    {
        if(!Mapped())
        {
            mRegionWhole=CRegion();
            Validate(EValid::REGION_WHOLE);
        }
        else if(!mParent)
        {
            mRegionWhole=CRect_d(0,0,mWholeRect.w(),mWholeRect.h());
            Validate(EValid::REGION_WHOLE);
        }
        else
        {
            mParent->RecalculateRegions();
        }
    }
    
    return mRegionWhole;
}

const CRegion &CWindow::GetClientRegion()
{
    if(!Valid(EValid::REGION_CLIENT))
    {
        if(!Mapped())
        {
            mRegionWhole=CRegion();
            Validate(EValid::REGION_CLIENT);
        }
        else
        {
            RecalculateRegions();
        }
    }

    return mRegionClient;
}

///////////////// coordinates & find windows ///////////////////

CPoint_d CWindow::TranslateCoords(const CPoint_d &pPoint,const PWindow &pOther)
{
    if(pOther)
        return pPoint+pOther->GetRootPos()-GetRootPos();
    else
        return pPoint-GetRootPos();
}

CPoint_d CWindow::DeviceToClient(const CPoint_d &pPoint)
{
    if(mClientMatrix)
        return mClientMatrix->I()*(pPoint-mClientRect.TopLeft());
    else
        return pPoint-mClientRect.TopLeft();
}

CPoint_d CWindow::ClientToDevice(const CPoint_d &pPoint)
{
    if(mClientMatrix)
        return mClientMatrix->F()*pPoint+mClientRect.TopLeft();
    else
        return pPoint+mClientRect.TopLeft();
}

void CWindow::ResetClientMatrix()
{
    if(mClientMatrix)
        mClientMatrix->SetIdentity();
}

void CWindow::SetClientMatrix(const mat::C2DTransform &pMatrix)
{
    if(mClientMatrix)
        *mClientMatrix=pMatrix;
    else
        mClientMatrix.reset(new mat::C2DTransform(pMatrix));
}

void CWindow::MultClientMatrix(const mat::C2DTransform &pMatrix)
{
    if(mClientMatrix)
        *mClientMatrix=pMatrix;
    else
        mClientMatrix.reset(new mat::C2DTransform(pMatrix));
}

void CWindow::ClientMatrixScale(const CSize_d &pSize)
{
    if(!mClientMatrix) mClientMatrix.reset(new mat::C2DTransform);

    mClientMatrix->Scale(pSize);
}

void CWindow::ClientMatrixTranslate(const CPoint_d &pPos)
{
    if(!mClientMatrix) mClientMatrix.reset(new mat::C2DTransform);
    
    mClientMatrix->Translate(pPos);
}

void CWindow::ClientMatrixRotate(double pA)
{
    if(!mClientMatrix) mClientMatrix.reset(new mat::C2DTransform);
    
    mClientMatrix->Rotate(pA);
}

//////////////////////////////// Focus ////////////////////////////

void CWindow::SetFocusMode(EFocusMode pMode)
{
    mFocusMode=pMode;
}

//SetFocus
//
//This function is called when the application wants to set the input
//focus within its top-level to this window, and, maybe, activate the window

bool CWindow::IsActive()
{
    CWindow *lActiveWindow=window_manager().ActiveWindow();
    
    if(!lActiveWindow) return false;
    
    return IsDescendantOf(*lActiveWindow);
}

void CWindow::Activate()
{
    if(mParent)
    {
        mParent->Activate();
        return;
    }
    
    if(mPhysical)
    {
        mPhysical->SetFocus();
    }
}

void CWindow::SetFocus(bool pActivate)
{
    if(HasFocus()) return;

    mFocusTimestamp=CCalendarClock::now();

    if(IsActive()&&Mapped())
        window_manager().SetFocusTo(this);
    else if(pActivate)
        Activate();
}

//This function is called by the application to set the input focus to whatever
//child should have it within this window

void CWindow::SetFocusToChild(bool pActivate)
{
    FindFocusedDescendant()->SetFocus(pActivate);
}

void CWindow::DropFocus()
{
    if(HasFocus())
    {
        CWindow *lTopLevel=GetTopLevel();
        if(lTopLevel==this)
            window_manager().SetFocusTo(nullptr);
        else
            window_manager().SetFocusTo(lTopLevel->FindFocusedDescendant());
    }
}

bool CWindow::CirculateFocus(ECirculate pMode,const PWindow &pStop,bool pFirst)
{
    if(!pFirst&&pStop==this) return false;
    if(!Mapped()) return false;

    if(pMode==ECirculate::FIRST)
    {
        if(FocusMode()&EFocusMode::TAB)
        {
            SetFocus();
            return true;
        }

        for(const PWindow &c : mChildren)
        {
            if(c->CirculateFocus(ECirculate::FIRST,pStop))
                return true;
        }

        return false;
    }
    else if(pMode==ECirculate::LAST)
    {
        ///\todo used reverse range
        for(int i=mChildren.size()-1;i>=0;i--)
        {
            if(mChildren[i]->CirculateFocus(ECirculate::LAST,pStop))
                return true;
        }

        if(FocusMode()&EFocusMode::TAB)
        {
            SetFocus();
            return true;
        }

        return false;
    }
    else if(pMode==ECirculate::NEXT)
    {
        ///\todo this
//         if(!HasFocus()&&!mFocusedChild) return false;
// 
//         int lFirst=-1;
// 
//         if(HasFocus())
//         {
//             lFirst=0;
//         }
//         else
//         {
//             for(int i=0;i<mChildren.size();i++)
//             {
//                 if(mChildren[i]==mFocusedChild)
//                 {
//                     lFirst=i+1;
//                     break;
//                 }
//             }
//         }
// 
//         if(lFirst<0) return false;
// 
//         for(int i=lFirst;i<mChildren.size();i++)
//         {
//             if(mChildren[i]->CirculateFocus(ECirculate::FIRST,pStop))
//                 return true;
//         }
// 
//         if(mParent)
//             return mParent->CirculateFocus(ECirculate::NEXT,pStop);
//         else
//             return CirculateFocus(ECirculate::FIRST,pStop);
    }
    else if(pMode==ECirculate::PREVIOUS)
    {
//         if(HasFocus()||!mFocusedChild) return false;
// 
//         int lFirst=-2;
// 
//         if(mFocusedChild)
//         {
//             for(int i=mChildren.size()-1;i>=0;i--)
//             {
//                 if(mChildren[i]==mFocusedChild)
//                 {
//                     lFirst=i-1;
//                     break;
//                 }
//             }
//         }
// 
//         if(lFirst<-1) return false;
// 
//         for(int i=lFirst;i>=0;i--)
//         {
//             if(mChildren[i]->CirculateFocus(ECirculate::FIRST,pStop))
//                 return true;
//         }
// 
//         if(FocusMode()&EFocusMode::TAB)
//         {
//             SetFocus();
//             return true;
//         }
// 
//         if(mParent)
//             return mParent->CirculateFocus(ECirculate::PREVIOUS,pStop);
//         else
//             return CirculateFocus(ECirculate::LAST,pStop);
    }

    return false;
}

bool CWindow::HasFocus()
{
    return window_manager().FocusWindow()==this;
}

///////////////////////////// Grabs ///////////////////////////

void CWindow::SetInputMode(EInputMode pMode)
{
    mInputMode=pMode;
}

//////////////////// Properties, flags and style //////////////////

void CWindow::SetBackColor(const img::CColor &pColor)
{
    mBackColor=pColor;
}

void CWindow::SetBackTransparent()
{
    mBackColor=img::CColor(0,0,0,0);
}

void CWindow::SetBackNone()
{
    mBackColor=img::CColor();
}

void CWindow::SetPointer(PPointer pPointer)
{
    if(mPointer==pPointer) return;

    mPointer=pPointer;

    window_manager().OnPointerChanged(this);
}

void CWindow::SetPointer(CPointer::EStock pIndex)
{
    SetPointer(CPointer::GetStock(pIndex));
}

void CWindow::SetTitle(const std::wstring &pTitle)
{
    mTitle=pTitle;

    if(mPhysical)
        mPhysical->SetTitle(pTitle);
}

void CWindow::SetIcon(PIcon pIcon)
{
    mIcon=pIcon;
    
    if(mPhysical)
        mPhysical->SetIcon(pIcon);
}

////////////////////////////////////////////////////////////////
//                                                            //
//                      static interface                      //
//                                                            //
////////////////////////////////////////////////////////////////

CWindow* CWindow::FindChildAtAndPos(CPoint_d &pPoint,const PWindow &pSkip)
{
    for(const PWindow &c : mChildren)
    {
        if(!c->Mapped()) continue;
        if(c==pSkip) continue;

        if(c->WholeRect().Contains(pPoint))
        {
            pPoint-=c->mWholeRect.TopLeft();
            return c->FindChildAtAndPos(pPoint,pSkip);
        }
    }

    return this;
}

//point is relative to window
CWindow* CWindow::FindChildAt(const CPoint_d &pPoint,const PWindow &pSkip)
{
    CPoint_d lPoint=pPoint;
    return FindChildAtAndPos(lPoint,pSkip);
}

//point is relative to root
CWindow* CWindow::FindChildAtRoot(const CPoint_d &pPoint,const PWindow &pSkip)
{
    CPoint_d lPoint=pPoint-GetRootPos();
    return FindChildAtAndPos(lPoint,pSkip);
}

std::vector<PWindow> CWindow::ManagedChildren()
{
    std::vector<PWindow> lReturn;
    
    for(const PWindow &c : mChildren)
    {
        if(c->GetFlag(EFlags::ATTACHED)) continue;
        if(!c->Mappable()) continue;
        if(!c->GetFlag(EFlags::MANAGED)) continue;
        
        lReturn.push_back(c);
    }
    
    return lReturn;
}

void CWindow::AddCommitHook(const std::function<void(const CRegion&)> &pHook)
{
    if(!mHooks) mHooks.reset(new SHooks);
    
    mHooks->mCommit.push_back(pHook);
}

////////////////////////////////////////////////////////////////
//                                                            //
//                          Internal                          //
//                                                            //
////////////////////////////////////////////////////////////////

////////////////////////// position & size /////////////////////

void CWindow::RecalculateRegions()
{
    //if we are here, either our client region or some child's whole region was not valid,
    //so recalculate no matter what.
    
    CRegion lRegion=GetWholeRegion();
    CPoint_d lPosInTopLevel=GetPosInTopLevel();
    
    for(const PWindow &c : mChildren)
    {
        if(!c->Visible()) continue;
        
        CRect_d lTopLevelRect(lPosInTopLevel+c->WholeRect().TopLeft(),c->WholeRect().Size());
        if(!c->Valid(EValid::REGION_WHOLE))
        {
            c->mRegionWhole=lRegion&lTopLevelRect;
            c->Validate(EValid::REGION_WHOLE);
        }
        
        lRegion-=lTopLevelRect;
    }
    
    if(!Valid(EValid::REGION_CLIENT))
    {
        mRegionClient=std::move(lRegion);
        Validate(EValid::REGION_CLIENT);
    }
}

///////////////////////////// Focus /////////////////////////

CWindow *CWindow::FindFocusedDescendant()
{
    CWindow *lReturn=this;
    
    for(const PWindow &c : mChildren)
    {
        CWindow *lDescendant=c->FindFocusedDescendant();
        if(lDescendant->mFocusTimestamp>lReturn->mFocusTimestamp)
            lReturn=lDescendant;
    }

    return lReturn;
}

img::CGC *CWindow::StartPainting(bool pClear)
{
    return StartPainting(nUnbounded,pClear);
}

img::CGC *CWindow::StartPainting(const CRegion &pRegion,bool pClear)
{
    if(!Mapped())
        throw XInvalid("can't repaint a non-mapped window");

    CWindow *lTopLevel=GetTopLevel();
    CRegion lRegion=pRegion+GetPosInTopLevel();
    lRegion&=GetWholeRegion();
    
    CExposeHelper *lHelper=lTopLevel->mPhysical->GetExposeHelper();
    return lHelper->Prepare(*this,lRegion,pClear);
}

void CWindow::EndPainting(img::CGC *pGC)
{
    EndPainting(pGC,nUnbounded);
}

void CWindow::EndPainting(img::CGC *pGC,const CRegion &pRegion)
{
    if(!Mapped())
        throw XInvalid("can't repaint a non-mapped window");

    CWindow *lTopLevel=GetTopLevel();
    CRegion lRegion=pRegion+GetPosInTopLevel();
    lRegion&=GetWholeRegion();
    
    CExposeHelper *lHelper=lTopLevel->mPhysical->GetExposeHelper();
    lHelper->Commit(lRegion);
}

void CWindow::DoLayout()
{
    if(!GetFlag(EFlags::MAPONLAYOUT)) return;
    
    SetFlag(EFlags::DOINGLAYOUT);

    //propagate region invalidation
    if(mParent&&!mParent->Valid(EValid::REGIONS))
        Invalidate(EValid::REGIONS);
    
    if(!GetFlag(EFlags::MAPPED)) //here we actually map it
    {
        SetFlag(EFlags::MAPPED);
        Refresh();
        //we should call onshow, but maybe later.
    }
    
    if(!Valid(EValid::EXTERNAL_LAYOUT))
    {
        msg_info() << "doing external layout\n";

        CRect_d lOldClientRect=mClientRect;

        if(mAttachManager)
            mClientRect=mAttachManager->DoLayout();
        else
            mClientRect=CRect_d(CPoint_d(0,0),mWholeRect.Size());
        
        if(mClientRect!=lOldClientRect)
        {
            CGeometryEvent lEvent;
            lEvent.mPos=mClientRect.TopLeft();
            lEvent.mSize=mClientRect.Size();
        
            OnSize(lEvent);
            
            Invalidate(EValid::INTERNAL_LAYOUT);
            Refresh();
        }

        Validate(EValid::EXTERNAL_LAYOUT);
    }
    
    if(!Valid(EValid::INTERNAL_LAYOUT))
    {
        msg_info() << "doing internal layout\n";
        
        OnLayout();

        //map non-managed, non-attached windows where they want to be
        for(const PWindow &c : mChildren)
        {
            if(c->GetFlag(EFlags::ATTACHED)) continue;
            if(c->GetFlag(EFlags::MANAGED)) continue;
            if(!c->Mappable()) continue;
            if(c->GetFlag(EFlags::MAPPED)||c->GetFlag(EFlags::MAPONLAYOUT)) continue;

            c->Map(c->mPosPreferred,c->mSizePreferred);
        }
        
        Validate(EValid::INTERNAL_LAYOUT);
    }

    UnsetFlag(EFlags::DOINGLAYOUT);
    
    for(const PWindow &c : mChildren)
    {
        c->DoLayout();
    }

    ///\todo really calculate layout
/*
    if(!Mapped()) return;
    CRect_d lPreviousClient=mClientRect;

    mGeometry.mClient={nZero,mGeometry.mSize};

    if(mAttachManager)
        mAttachManager->ReduceClientRect(mGeometry.mClient);

    for(int i=0;i<mLayoutManagers.size();i++)
        mLayoutManagers[i]->ReduceClientRect(mGeometry.mClient);

    //if the client size has changed
    if(lPreviousClient!=mGeometry.mClient)
    {
        mRect=CRect_d(CPoint_d(0,0),mGeometry.mClient.Size());

        if(!GetFlag(FLAG_CREATED)) return;

        InvalidateRegions(REGION_CLIENTVISIBLE);

        SendSizeEvent();

        for(int i=0;i<mChildren.size();i++)
        {
            if(mChildren[i]->GetFlag(FLAG_NONCLIENTGEOMETRY)) continue;
            if(mChildren[i]->mGeometry.mRequestedPos.Undefined()) continue;
            mChildren[i]->Move(mChildren[i]->mGeometry.mRequestedPos,true);
        }

        if(GetFlag(FLAG_VISIBLE))
        {
            GetExposeHelper()->DiscardBuffer();
            Refresh();
        }
    }
*/
}

void CWindow::DoRefresh(const CRegion &pRegion)
{
    //pRegion is the region that has been marked as damage by the window system
    using std::swap;
    
    if(!Mapped())
    {
        msg_warn() << "this should probably never happen now\n";
        return;
    }

    CRegion lCommit=pRegion&GetWholeRegion();
    CRegion lExpose=lCommit;
    CExposeHelper *lHelper=mPhysical->GetExposeHelper();
    ProcessExpose(lHelper,lExpose,lCommit);
    lHelper->Commit(lCommit);
    if(mHooks&&!mHooks->mCommit.empty())
    {
        for(const auto &lHook : mHooks->mCommit)
        {
            lHook(lCommit);
        }
    }
}

void CWindow::ProcessMap(const CRect_d &pRect)
{
    if(mParent&&!mParent->GetFlag(EFlags::MAPONLAYOUT)) return;
    
    //this is only for toplevel windows
    SetFlag(EFlags::MAPONLAYOUT);
    
    if(Mapped()) //it is already mapped
    {
        if(pRect==mWholeRect) //it is a no op
            return;
        
        if(pRect.Size()==mWholeRect.Size()) //size didn't change
        {
            mWholeRect=pRect;
            if(!mParent)
                return;

            Invalidate(EValid::REGIONS);
            mParent->Invalidate(EValid::REGION_CLIENT);
            Refresh();
            return;
        }
    }

    //size changed or it was newly mapped
    mWholeRect=pRect;
    Invalidate(EValid::EXTERNAL_LAYOUT);
    Invalidate(EValid::REGIONS);
    if(mParent)
        mParent->Invalidate(EValid::REGION_CLIENT);
    
    if(!mParent)
        window_manager().mLayout.insert(this);
    else if(!mParent->GetFlag(EFlags::DOINGLAYOUT)) //we must do a layout so that it takes effect
        window_manager().mLayout.insert(GetTopLevel());
    
    Refresh();
    
    /*
    if(!Mapped())
    msg_info() << "processing map of " << this << "\n";
    if(!GetFlag(EFlags::FIRSTSHOW))
    {
        SetFlag(EFlags::FIRSTSHOW);
        OnFirstShow();
    }
    
    SetFlag(EFlags::MAPPED);
    OnShow();
    
    if(IsActive()) //see if this window should get the focus
    {
        CWindow *lFocused=window_manager().FocusWindow();
        if(!lFocused||mFocusTimestamp>lFocused->mFocusTimestamp)
            window_manager().SetFocusTo(this);
    }
    */
}

void CWindow::ProcessUnmap()
{
    if(!GetFlag(EFlags::MAPONLAYOUT))
    {
        msg_warn() << "ProcessUnmap called for non-mapped window\n";
        return;
    }
    
    //we are here because either we were hidden, or our parent was unmapped, or we don't have a parent anymore
    ///\todo see if we have to remove this as the pointer window... maybe not directly, but this should be a motion event for
    ///another window, if there's anything left
    
    DropFocus();

    UnsetFlag(EFlags::MAPPED);
    UnsetFlag(EFlags::MAPONLAYOUT);
    if(mParent)
        mParent->Invalidate(EValid::REGION_CLIENT);

    //no events that could cause a window to be deleted should be thrown from here. events should better be enqueued
    for(const PWindow &c : mChildren)
    {
        if(c->GetFlag(EFlags::MAPONLAYOUT))
            c->ProcessUnmap();
    }

}

void CWindow::ProcessExpose(CExposeHelper *pHelper,const CRegion &pDirtyRegion,CRegion &pCommitRegion)
{
    if(!Mapped()) return;
    
    ///\todo here if the window is buffered, just push the buffer, or maybe even delay until commit
    
    if(!mRegionDirty.empty()||!pDirtyRegion.empty())
    {
        CRegion lDirtyRegion;
        using std::swap;
        swap(lDirtyRegion,mRegionDirty);

        if(Visible()) //it might not be because of the paint mode
        {
            if(lDirtyRegion.Unbounded()||pDirtyRegion.Unbounded())
            {
                lDirtyRegion=GetClientRegion();
            }
            else
            {
                lDirtyRegion+=GetPosInTopLevel();
                lDirtyRegion|=pDirtyRegion;
                lDirtyRegion&=GetClientRegion();
            }

            if(mEvents&EVENT_EXPOSE)
                OnPreExpose();

            if(!lDirtyRegion.empty())
            {
                img::CGC *lExposeGC=pHelper->Prepare(*this,lDirtyRegion,true);

                if(mClientMatrix)
                    lExposeGC->Transform(*mClientMatrix);

                CExposeEvent lE;
                lE.mGC=lExposeGC;
                lE.mRegion=lDirtyRegion;
                lE.mRect=CRect_d(lDirtyRegion.BoundingBox())-GetPosInTopLevel()-mClientRect.TopLeft();
                ///\todo
        /*
                if(mAttachManager)
                    mAttachManager->OnExpose(&lE);
                for(int i=0;i<mLayoutManagers.size();i++)
                    mLayoutManagers[i]->OnExpose(&lE);
        */
                OnExpose(lE);

                pCommitRegion|=lDirtyRegion;
            }
        }
    }

    for(const PWindow &c : mChildren)
        c->ProcessExpose(pHelper,pDirtyRegion,pCommitRegion);
}

void CWindow::SendSizeEvent()
{
    ///\todo why is this istill here?
//    for(int i=0;i<mLayoutManagers.size();i++)
//        mLayoutManagers[i]->OnSize();

    ///\todo do it with window manager, which is next
    //mLoopFunction->mOnSize.insert(this);
}

void CWindow::SendChildEvent(CWindow &pWindow,int pEvent)
{
    if(pWindow.GetFlag(EFlags::ATTACHED))
        mAttachManager->OnChild(&pWindow,pEvent);

    OnChild(pWindow,pEvent);
}

//////////////////////////// Event selection /////////////////////////

void CWindow::SetEvents(EEvents pE)
{
    mEvents=pE;

    if(mPhysical)
        mPhysical->SelectInput(mEvents);
}

/*namespace gui*/ } /*namespace buola*/ }
